﻿//#define CallBack//通过预定义，切换回调函数和线程
//#define EasyModel//简单模式，简单模式速度慢，转换简单，复杂模式，速度快，需要拆通道，代码复杂
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using HalconDotNet;
using MVSDK;
using CameraHandle = System.Int32;//别名
using MvApi = MVSDK.MvApi;//别名

/****************************************************************
*   类名：MindVisionSDK
*   命名空间：MindVisionSDK
*   作者：X
*   CLR版本：4.0.30319.42000
*   创建时间：2022/3/9 9:41:31
*   描述说明：
*   修改历史：
*****************************************************************/
namespace MindVisionSDK
{
    public delegate void Action<HObject>(HObject image);//声明委托，用于传递图像
    public class MindVisionSDK
    {
        private bool threadStatus = true;//是否抓取标识符
        private Thread threadGrabImage;//抓取图像线程
        private HObject h_imageRGB, h_imageGray;//灰度图像和彩色图像
        protected CameraHandle h_Camera;//相机句柄
        protected tSdkCameraCapbility tCameraCapability;//相机特性
        protected CameraSdkStatus status;//相机状态
        protected tSdkCameraDevInfo[] cameraDevInfo;//相机信息结构体
        protected IntPtr m_ImageBuffer;//RGB通道图像缓存
        protected CAMERA_SNAP_PROC m_CaptureCallback;//图像捕获的回调函数
        protected IntPtr m_iCaptureCallbackCtx;     //图像回调函数的上下文参数
        protected CAMERA_SNAP_PROC tempCallBack = null;//用于保存当前的回调函数，可以为空
        public event Action<HObject> Trans;//传递图像事件
        /// <summary>
        /// 初始化相机
        /// </summary>
        /// <returns></returns>
        public bool InitCamera(IntPtr handle)
        {
            if (h_Camera > 0) return true;//通过句柄数量判断是否初始化
            status = MvApi.CameraEnumerateDevice(out cameraDevInfo);//枚举相机设备
            if (status == CameraSdkStatus.CAMERA_STATUS_SUCCESS)//判断是否成功
            {
                if (cameraDevInfo != null)
                {
                    status = MvApi.CameraInit(ref cameraDevInfo[0], -1, -1, ref h_Camera);//初始化第一个相机
                    if (status == CameraSdkStatus.CAMERA_STATUS_SUCCESS)//初始化成功
                    {
                        //获得相机特性
                        MvApi.CameraGetCapability(h_Camera, out tCameraCapability);
                        if (tCameraCapability.sIspCapacity.bMonoSensor != 0)//判断是否是黑白相机
                        {
                            // 黑白相机输出8位灰度数据
                            MvApi.CameraSetIspOutFormat(h_Camera, (uint)MVSDK.emImageFormat.CAMERA_MEDIA_TYPE_MONO8);
                        }
                        //设置图像缓存
                        m_ImageBuffer = Marshal.AllocHGlobal(tCameraCapability.sResolutionRange.iWidthMax * tCameraCapability.sResolutionRange.iHeightMax * 3 + 1024);
                        //设置触发模式
                        MvApi.CameraSetTriggerMode(h_Camera,1);//0表示连续模式，1是软触发，2是硬触发。
                        MvApi.CameraCreateSettingPage(h_Camera, handle, cameraDevInfo[0].acFriendlyName,/*SettingPageMsgCalBack*/null,/*m_iSettingPageMsgCallbackCtx*/(IntPtr)null, 0);
#if CallBack//使用回调函数采图
                        m_CaptureCallback = new CAMERA_SNAP_PROC(ImageCaptureCallback);//注册回调函数
                        //设置回调函数，将回调函数传入
                        MvApi.CameraSetCallbackFunction(h_Camera, m_CaptureCallback, m_iCaptureCallbackCtx, ref tempCallBack);
#else//使用线程采图
                        //注意：在使用线程采图时，需要先打开SDK，再运行线程，否则会一直超时
                        threadGrabImage = new Thread(() => { CaptureImage_Task(); });
                        threadGrabImage.IsBackground = true;
#endif
                        return true;
                    }
                    else
                    {
                        h_Camera = 0;//相机句柄
                        Console.WriteLine("相机初始化错误，错误码{0}", status);
                        return false;
                    }
                }
            }
            else
            {
                Console.WriteLine($"state:{status},没有找到相机!");
                return false;
            }
            return true;
        }
        /// <summary>
        /// 让SDK进入工作模式，开始接收来自相机发送的图像
        /// </summary>
        public void SDK_Start()
        {
            if (h_Camera > 0)
            {
                MvApi.CameraPlay(h_Camera);
                if (threadGrabImage!=null)
                {
                    if (!threadGrabImage.IsAlive)
                    {
                        threadGrabImage.Start();//初始化时，打开抓取线程，通过线程标识符来抓取图像
                    }
                    threadStatus = true;
                }
            }
            else
                Console.WriteLine("相机未初始化！");
        }
        /// <summary>
        /// SDK暂停工作
        /// </summary>
        public void SDK_Pause()
        {
            if (h_Camera > 0)
                MvApi.CameraPause(h_Camera);
            else
                Console.WriteLine("相机未初始化！");
        }
        /// <summary>
        /// SDK停止工作
        /// </summary>
        public void SDK_Stop()
        {
            if (h_Camera > 0)
                MvApi.CameraStop(h_Camera);
            else
                Console.WriteLine("相机未初始化！");
        }
        public void SDK_StopAndDispose()
        {
            if (h_Camera > 0)
            {
#if !CallBack
                threadStatus = false;
#endif
                MvApi.CameraStop(h_Camera);//停止SDK
                MvApi.CameraUnInit(h_Camera);//反初始化。相机反初始化。释放资源。 
                Marshal.FreeHGlobal(m_ImageBuffer);//释放资源
                h_Camera = 0;
            }
            else
                Console.WriteLine("相机未初始化！");
        }
        /// <summary>
        /// 单次取像
        /// </summary>
        /// <param name="image"></param>

#if CallBack
        /// <summary>
        /// 回调函数的方法实现
        /// </summary>
        /// <param name="hCamera">相机句柄</param>
        /// <param name="pFrameBuffer">  输入图像数据的缓冲区地址</param>
        /// <param name="pFrameHead">输入图像的帧头信息，处理完成后，帧头信息中的图像格式uiMediaType会随之改变
        /// 一般是图像的宽高</param>
        /// <param name="pContext"></param>
        private void ImageCaptureCallback(int hCamera, IntPtr pFrameBuffer, ref tSdkFrameHead pFrameHead, IntPtr pContext)
        {
            //图像处理，将原始输出转换为RGB格式的位图数据，同时叠加白平衡、饱和度、LUT等ISP处理。
            MvApi.CameraImageProcess(hCamera, pFrameBuffer, m_ImageBuffer, ref pFrameHead);
            TransImage(ConvertToHalcon(pFrameHead, m_ImageBuffer));
        }
#else
        /// <summary>
        /// 通过事件采集图片
        /// </summary>
        private void CaptureImage_Task()
        {
            tSdkFrameHead pFrameHead;
            IntPtr pFrameBuffer;//pFrameBuffer由SDK内部申请。应用层不要调用delete之类的释放函数
            CameraSdkStatus tempStatus;
            while (threadStatus)
            {
                //输入参数：相机句柄，返回图像头文件，返回图像数据，超时时间
                tempStatus = MvApi.CameraGetImageBuffer(h_Camera, out pFrameHead, out pFrameBuffer, 500);
                if (tempStatus == CameraSdkStatus.CAMERA_STATUS_SUCCESS)//如果是触发模式，需要考虑是否超时
                {
                    //图像处理，将原始输出转换为RGB格式的位图数据，同时叠加白平衡、饱和度、LUT等ISP处理。
                    MvApi.CameraImageProcess(h_Camera, pFrameBuffer, m_ImageBuffer, ref pFrameHead);
                    TransImage(ConvertToHalcon(pFrameHead,m_ImageBuffer));
                    //成功调用CameraGetImageBuffer后必须释放，下次才能继续调用CameraGetImageBuffer捕获图像。
                    MvApi.CameraReleaseImageBuffer(h_Camera, m_ImageBuffer);
                }
                else
                {
                    Console.WriteLine("取像错误！可能超时！");
                }
            }
        }
#endif

        /// <summary>
        /// 设置需要显示的窗口
        /// </summary>
        /// <param name="handle"></param>
        /// <param name="Width"></param>
        /// <param name="Height"></param>
        public void SetShowHandle(IntPtr handle, int Width, int Height)
        {
            //初始化显示模块，使用SDK内部封装好的显示接口
            MvApi.CameraDisplayInit(h_Camera, handle);
            MvApi.CameraSetDisplaySize(h_Camera, Width, Height);
        }

        /// <summary>
        /// 将相机采集到的图像转换成HObject类型
        /// </summary>
        /// <param name="pFrameHead">图像的头文件，包含图像宽高</param>
        /// <param name="pFrameBuffer">图像数据缓存</param>
        /// <returns></returns>
        private HObject ConvertToHalcon(tSdkFrameHead pFrameHead, IntPtr m_ImageBuffer)
        {
            HObject image = new HObject();
#if EasyModel
            //注意“bgr”是C#中的色彩模式，在halcon中是rgb,这一点是要注意的！
            HOperatorSet.GenImageInterleaved(out image, m_ImageBuffer, "bgr", pFrameHead.iWidth, pFrameHead.iHeight, -1, "byte", 0, 0, 0, 0, -1, 0);
#else//第二种复杂方法，速度快，需要拆通道，代码复杂        
            int bytesBylineGray = (pFrameHead.iWidth + 3) / 4 * 4;//灰度图像（单通道图像）每行所占字节数

            MVSDK.emImageFormat imageType = (MVSDK.emImageFormat)pFrameHead.uiMediaType;//获取图像类型，24位还是8位还是其他
            //目前只支持8位和24位
            if (imageType == MVSDK.emImageFormat.CAMERA_MEDIA_TYPE_MONO8)//灰度图像
            {
                byte[] buffer = new byte[bytesBylineGray * pFrameHead.iHeight];//单通道图像整幅图所占的字节数
                byte[] bufferGray = new byte[bytesBylineGray * pFrameHead.iHeight];//灰度图整幅图所占的字节数
                Marshal.Copy(m_ImageBuffer, bufferGray, 0, bytesBylineGray * pFrameHead.iHeight);
                unsafe//需要在属性中允许不安全的代码才能执行
                {
                    fixed (byte* imageGray = bufferGray)//禁止垃圾回收器GC重定位可移动的变量
                    {
                        //数据字节转HObject算子：gen_image1_extern
                        //需要输入的参数：图像的宽高，单个通道的图像，rgb
                        HOperatorSet.GenImage1Extern(out h_imageGray, "byte", pFrameHead.iWidth, pFrameHead.iHeight, new IntPtr(imageGray), 0);
                        image = h_imageGray;
                    }
                }
            }
            else if (imageType == MVSDK.emImageFormat.CAMERA_MEDIA_TYPE_BGR8)//彩色图像24位
            {

                int bytesBylineRgb = (pFrameHead.iWidth * 3 + 3) / 4 * 4;//彩色图像每行所占字节数
                byte[] buffer = new byte[bytesBylineRgb * pFrameHead.iHeight];//3通道图像整幅图所占的字节数
                byte[] bufferRGB = new byte[bytesBylineRgb * pFrameHead.iHeight];//彩色色图像整幅图所占的字节数
                byte[] bufferR = new byte[bytesBylineGray * pFrameHead.iHeight];//R通道的图像所占的字节数
                byte[] bufferG = new byte[bytesBylineGray * pFrameHead.iHeight];//G通道的图像所占的字节数
                byte[] bufferB = new byte[bytesBylineGray * pFrameHead.iHeight];//B通道的图像所占的字节数
                Marshal.Copy(m_ImageBuffer, buffer, 0, bytesBylineRgb * pFrameHead.iHeight);//复制图像数据
                //遍历图像，对图像进行拆通道
                //拆通道:将pFrameBuffer结构体转换成HObject对象
                //C\#中每行字节数必须是4的倍数：公式：(((（图像宽\*图像位深度）/8)+3)/4)\*4。注意：/4是除以4再取整
                for (int i = 0; i < pFrameHead.iHeight; i++)
                {
                    for (int j = 0; j < pFrameHead.iWidth; j++)
                    {
                        //将每个单元RGB，拆分成，B,G,R，相对于之前位置1，变成1,2,3，即位置在原来的基础上+0，+1，+2。
                        //相当于Halcon中的decompose3算子
                        bufferB[i * bytesBylineGray + j] = buffer[i * bytesBylineRgb + j * 3 + 0];//蓝色通道
                        bufferG[i * bytesBylineGray + j] = buffer[i * bytesBylineRgb + j * 3 + 1];//绿色通道
                        bufferR[i * bytesBylineGray + j] = buffer[i * bytesBylineRgb + j * 3 + 2];//红色通道
                        //彩色图像转灰度图像，公式： gray = 0.299 * red + 0.587 * green + 0.114 * blue .
                        //相当于Halcon中的rgb1_to_gray算子，如果需要彩色图像就不需要调用下面
                        //bufferGray[i * bytesBylineGray + j] = (byte)(bufferR[i * bytesBylineGray + j] * 0.3 + bufferG[i * bytesBylineGray + j] * 0.5 + bufferB[i * bytesBylineGray + j] * 0.11);
                    }
                }
                unsafe//需要在属性中允许不安全的代码才能执行
                {
                    fixed (byte* imageBufferR = bufferR, imageBufferG = bufferG, imageBufferB = bufferB)//禁止垃圾回收器GC重定位可移动的变量
                    {
                        //数据字节转HObject算子：gen_image3_extern
                        //需要输入的参数：图像的宽高，三个通道的图像，rgb
                        HOperatorSet.GenImage3Extern(out h_imageRGB, "byte", pFrameHead.iWidth, pFrameHead.iHeight,
                            new IntPtr(imageBufferR), new IntPtr(imageBufferG), new IntPtr(imageBufferB), 0);
                        //也可以用gen_image1_extern：只需要传入单个通道图像
                        image = h_imageRGB;
                    }
                }
            }
            else
            {
                Console.WriteLine("图像位深度错误！");
            }
#endif           
            return image;
        }
        /// <summary>
        /// 软触发
        /// </summary>
        public void SoftTrigger()
        {
            if (h_Camera>0) MvApi.CameraSoftTriggerEx(h_Camera, 1);
        }
        private void TransImage(HObject image) => Trans.Invoke(image);
    }
}
